﻿namespace CloudNinja.Metering.Providers.SQLAzure
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using CloudNinja.Metering.Common;
    using CloudNinja.Metering.Common.Configuration;
    using CloudNinja.Metering.Common.Logging;
    using CloudNinja.Metering.Data;

    using Microsoft.Practices.EnterpriseLibrary.TransientFaultHandling;
    using Microsoft.Practices.EnterpriseLibrary.WindowsAzure.TransientFaultHandling.SqlAzure;

    using MeterData = CloudNinja.Metering.Data.Models.MeterData;

    [ProviderConfigElementType("CloudNinja.Metering.Providers.Configuration.DatabaseBandwidth.DatabaseBandwidthMeteringProviderElement, CloudNinja.Metering.Providers, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null")]
    public class DatabaseBandwidthUsageProvider : MeteringProviderBase
    {
        public const string MeterTypeDatabaseBandwidthIngress = "dbi";
        public const string MeterTypeDatabaseBandwidthEgress = "dbe";

        private readonly Dictionary<string, string> databaseServers;

        private readonly ILogger logger;

        public DatabaseBandwidthUsageProvider(TimeSpan collectInterval, Dictionary<string, string> databaseServers, ILogger logger, IMeteringRepository repository, IMeteringContext meteringContext)
            : base(collectInterval, repository, logger, meteringContext)
        {
            this.databaseServers = databaseServers;
            this.logger = logger;
        }

        public override string Name
        {
            get { return "DatabaseBandwidthUsageProvider"; }
        }

        public override void Initialize()
        {
            this.logger.LogInformation("Initialized DatabaseBandwidthUsage Provider");
        }

        public override void Collect()
        {
            var results = new Dictionary<string, TenantDatabaseBandwidth>();
            var lockObject = new object();

            this.StartTiming();
            using (var sem = new SemaphoreSlim(1))
            {
                Parallel.ForEach(
                    this.databaseServers.Keys,
                    () => new Dictionary<string, TenantDatabaseBandwidth>(),
                    (connStr, loopstate, localDict) =>
                    {
                        var databaseNames = this.GetDatabases(string.Format("{0};Initial Catalog=master", connStr));

                        foreach (var databaseName in databaseNames)
                        {
                            var tenantId = Helper.GetTenantId(databaseName, this.databaseServers[connStr]);
                            if (string.IsNullOrEmpty(tenantId))
                            {
                                continue;
                            }

                            var databaseBandwidth =
                                TenantDatabaseBandwidth.GetTodays(
                                    string.Format("{0};Initial Catalog=master", connStr), databaseName, logger);
                            localDict.Add(tenantId, databaseBandwidth);
                        }

                        return localDict;
                    },
                    finalResult =>
                    {
                        lock (lockObject)
                        {
                            foreach (var l in finalResult)
                            {
                                results.Add(l.Key, l.Value);
                            }
                        }
                    });
            }

            this.MeteringRepository.Write(
                results.SelectMany(
                    x =>
                    new List<MeterData>
                        {
                            new MeterData
                                {
                                    MeterTypeId = MeterTypeDatabaseBandwidthEgress,
                                    TenantId = x.Key,
                                    Value = x.Value.DatabaseBandwidth_Egress,
                                    TimePeriod = DateTime.UtcNow,
                                    Count = 0
                                },
                            new MeterData
                                {
                                    MeterTypeId = MeterTypeDatabaseBandwidthIngress,
                                    TenantId = x.Key,
                                    Value = x.Value.DatabaseBandwidth_Ingress,
                                    TimePeriod = DateTime.UtcNow,
                                    Count = 0
                                },
                        }));

            this.StopTiming();
        }

        public override void Stop()
        {
            // We have nothing we can or need to end/stop here.
        }

        private IEnumerable<string> GetDatabases(string connStr)
        {
            var databaseNames = new List<string>();


            using (var connection = new ReliableSqlConnection(connStr, this.MeteringContext.SqlAzureConnectionRetryPolicy, this.MeteringContext.SqlAzureCommandRetryPolicy))
            {
                using (var command = connection.CreateCommand())
                {
                    try
                    {
                        connection.Open();

                        command.CommandText = @"select name from sys.databases";
                        command.CommandType = System.Data.CommandType.Text;

                        using (var reader = command.ExecuteReaderWithRetry(this.MeteringContext.SqlAzureCommandRetryPolicy))
                        {
                            while (reader.Read())
                            {
                                databaseNames.Add((string)reader["name"]);
                            }
                        }
                    }
                    catch (Exception exception)
                    {
                        this.logger.LogException(exception);
                    }
                }
            }

            return databaseNames;
        }
    }
}
